package com.backapp.app;

import java.io.*;
import java.util.*;
import java.util.zip.*;

import org.apache.commons.compress.archivers.*;
import org.apache.commons.compress.archivers.tar.*;
import org.apache.commons.compress.compressors.gzip.*;
import org.apache.commons.compress.utils.*;

import android.content.*;
import android.os.*;
import android.util.*;
import android.widget.*;

import com.dropbox.client2.session.Session.*;

public class Utils
{
	final static public String DROPBOX_KEY = "vakqldg5snfa1uc";
	final static public String DROPBOX_SECRET = "zhybrs08wo9byr4";
	
	//
	// Need to access only the app folder.
	//
	
	public static final AccessType ACCESS_TYPE = AccessType.APP_FOLDER;
	public static final String PREFS_NAME = "sprefs";
	public static final String PREF_NAME_KEY = "DROPBOX_KEY";
	public static final String PREF_NAME_SECRET = "DROPBOX_SECRET";
	public static final String LOG_TAG = "BACKAPP";
	
	public static final File SDCARD = Environment.getExternalStorageDirectory();
	
	/**
	 * Print a toast message.
	 * 
	 * @param message supplies the message to be printed.
	 */
	public static void 
	toastMessage(
		Context context,
		String message
		)
	{
		Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
	}
	
	public static void
	logInfo(
		String logString
		)
	{
		Log.i(LOG_TAG, logString);
	}
	
	public static void 
	logError(
		String logString
		)
	{
		Log.e(LOG_TAG, logString);
	}
	
    /**
     * Creates the backapp directory.
     * 
     * @throws Exception throws an exception if the directory could not be created. 
     */
    public static void
    createBackAppDir() 
	throws Exception
    {
    	File targetDir = null;
    	boolean targetDirCreated;
    	
    	try
		{
			targetDir = new File(SDCARD.getCanonicalPath() + "/backapp/");
		} 
    	catch (IOException e)
		{
			throw new Exception("Could not open file: " + e.getMessage());
		}
    	
    	if (!targetDir.exists())
    	{
    		targetDirCreated = targetDir.mkdir();
    		
    		if (!targetDirCreated)
    		{
    			Utils.logInfo("backup directory could not be created.");
    			throw new Exception("backup directory could not be created.");
    		}
    	}
    	else
    	{
    		Utils.logInfo("backup directory exists.");
    	}
    }
    
    /**
     * Copies a file to the '/backapp' directory.
     * 
     * @param src supplies the file handle for the file to be copied.
     */
    public static void
    copyFileToBackappDir(
		File src,
		String appName
		)
    {
    	File dest;
    	String destPath;
    	
    	FileInputStream fis;
    	FileOutputStream fos;
    	
    	byte[] buffer;
    	int numBytes;
    	
    	destPath = src.getName();
    	
    	try
		{
    		destPath = Environment.getExternalStorageDirectory().getCanonicalPath() 
					+ "/backapp/" + appName + "." + src.getName();
    		
			dest = new File(destPath);
			
			if (!dest.exists())
			{
				if (!dest.createNewFile())
				{
					throw new FileNotFoundException(destPath);
				}
			}
			
			fis = new FileInputStream(src);
			fos = new FileOutputStream(dest, false);
			
			buffer = new byte[1024];
			numBytes = 0;
			
			while ((numBytes = fis.read(buffer)) != -1)
			{
				fos.write(buffer);
			}
			
			Utils.logInfo("File copied");
			
			fis.close();
			fos.close();
		} 
    	catch (IOException e)
		{
    		Utils.logError("Could not open file handle: " + destPath);
		}
    }
    
    public static void
    copyAppData(
		AppDetails appDetails
		)
	throws Exception
    {
    	Utils.logInfo("Creating archive for: " + appDetails.getName());
		
    	createBackAppDir();
    	
		executeTar(
			appDetails.getDataDir(), 
			SDCARD.getCanonicalPath() + "/backapp/" + appDetails.getName() +
				"." + appDetails.getPackagaeName() + ".tar.gz"
			);
		
		copyFileToBackappDir(new File(appDetails.getApkSource()), appDetails.getName());
	}
        
    private static boolean
    executeTar(
		String path,
		String targetPath
		)
    	throws Exception
    {
    	String command;
    	
    	command = "tar -cvzf " + targetPath + " " + path;
    	
        return executeCommand(command);
    }

    public static boolean
    executeUnTar(
		String tarGZ
		)
    	throws Exception
    {
    	String command;
    	
    	command = "tar -xvzf " + tarGZ + " -C /";
    	
    	return executeCommand(command);
    }

    private static String
    getPackageName(
		String tarGZ
		)
    {
    	String[] split = tarGZ.split("\\/");
    	return split[split.length - 1];
    }
    
    public static String 
    getAppId(
		String packageName
		)
    	throws Exception
    {
    	String command;
    	File idFile;
    	BufferedReader reader;
    	String appID;
    	
    	packageName = getPackageName(packageName);
    	
    	command = "ls -l /data/data/ | grep " + packageName + 
			" | cut -f2 -d' ' > " + SDCARD.getAbsolutePath() + "/tmp/id";
    	
    	if (!executeCommand(command))
    	{
    		throw new Exception("Error executing: " + command);
    	}
    	
        idFile = new File(SDCARD.getAbsolutePath() + "/tmp/id");
        
        if (!idFile.exists())
        {
        	throw new Exception("ID File not found");
        }
        
        reader = new BufferedReader(new FileReader(idFile));
        appID = reader.readLine();
        reader.close();
        
        return appID;
    }
    
    private static boolean 
    executeCommand(
		String command
		)
    	throws Exception
    {
    	boolean ret = true;
    	java.lang.Process suProcess = Runtime.getRuntime().exec("su");
    	DataOutputStream os = new DataOutputStream(suProcess.getOutputStream());
    	
    	os.writeBytes(command + "\n");
        os.flush();

        os.writeBytes("exit\n");
        os.flush();

        try
        {
            int suProcessRetval = suProcess.waitFor();

            if (255 != suProcessRetval)
            {
                    // Root access granted
                    ret = true;
            } else
            {
                    // Root access denied
                    ret = false;
            }
        } 
        catch (Exception e)
        {
            Utils.logError("Error executing root action: " + e.getMessage());
        }

        return ret;
    }
    
    private static void
    changePerm(
		String id,
		String packageName
		)
    	throws Exception
    {
    	String command;
    	
    	command = "find /data/data/" + packageName + "/ -exec chown " + id + " {} \\;";
    	
    	if (!executeCommand(command))
    	{
    		throw new Exception("Error executing: " + command);
    	}
    	
    	command = "find /data/data/" + packageName + "/ -exec chgrp " + id + " {} \\;";
    	
    	if (!executeCommand(command))
    	{
    		throw new Exception("Error executing: " + command);
    	}
    }
    
    public static void
    extractTarPackage(
		String packagePath
		)
    	throws Exception
    {
    	String id;
    	
    	id = getAppId(packagePath.replace(".tar.gz", ""));
    	executeUnTar(packagePath);
    	changePerm(id, getPackageName(packagePath.replace(".tar.gz", "")));
    }
    
	/**
	 * Creates a tar.gz file at the specified path with the contents of the
	 * specified directory.
	 * 
	 * @param dirPath supplies the path to the directory to create an archive of
	 * @param archivePath supplies the path to the archive to create
	 * 
	 * @throws IOException If anything goes wrong
	 */
	public static void 
	createTarGzOfDirectory(
		String directoryPath,
		String tarGzPath
		) 
	{
		FileOutputStream fOut = null;
		BufferedOutputStream bOut = null;
		GzipCompressorOutputStream gzOut = null;
		TarArchiveOutputStream tOut = null;

		try
		{
			fOut = new FileOutputStream(new File(tarGzPath));
			bOut = new BufferedOutputStream(fOut);
			gzOut = new GzipCompressorOutputStream(bOut);
			tOut = new TarArchiveOutputStream(gzOut);

			addFileToTarGz(tOut, directoryPath, "");
		}
		catch (Exception e)
		{
			Utils.logError("Exception creating archive: " + directoryPath + " " + e.getMessage());
		}
		finally
		{
			try
			{
				tOut.finish();
				tOut.close();
				gzOut.close();
				bOut.close();
				fOut.close();
			}
			catch (IOException e)
			{
			}
		}
	}

	/**
	 * Creates a tar entry for the path specified with a name built from the
	 * base passed in and the file/directory name. If the path is a directory, a
	 * recursive call is made such that the full directory is added to the tar.
	 * 
	 * @param tOut supplies the tar file's output stream.
	 * @param path supplies the filesystem path of the file/directory being added
	 * @param base supplies the base prefix to for the name of the tar file entry
	 * 
	 * @throws IOException If anything goes wrong
	 */
	private static void 
	addFileToTarGz(
		TarArchiveOutputStream tOut,
		String path, 
		String base
		) 
		throws IOException
	{
		File f = new File(path);
		
		Utils.logInfo("Adding file to the tar: " + f.getAbsolutePath());

		String entryName = base + f.getName();
		TarArchiveEntry tarEntry = new TarArchiveEntry(f, entryName);

		tOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
		tOut.putArchiveEntry(tarEntry);

		if (f.isFile())
		{
			IOUtils.copy(new FileInputStream(f), tOut);

			tOut.closeArchiveEntry();
		} 
		else
		{
			tOut.closeArchiveEntry();
			File[] children = f.listFiles();

			if (children != null)
			{
				for (File child : children)
				{
					addFileToTarGz(tOut, child.getAbsolutePath(), entryName
							+ "/");
				}
			}
		}
	}
	
	public static void 
	extract(
		String dataZip
		) 
		throws Exception
	{
		File fileDataZip = new File(dataZip);
		
		unGzip(
			new File(dataZip), 
			fileDataZip.getParentFile().getAbsoluteFile()
			);

		dataZip = dataZip.replace(".gz", "");

		unTar(new File(dataZip), new File("/data/data/"));
	}

	/**
	 * Untar an input file into an output file.
	 * 
	 * The output file is created in the output folder, having the same name as
	 * the input file, minus the '.tar' extension.
	 * 
	 * @param inputFile the input .tar file
	 * @param outputDir the output directory file.
	 * 
	 * @throws IOException
	 * @throws FileNotFoundException
	 * 
	 * @return The {@link List} of {@link File}s with the untared content.
	 * @throws ArchiveException
	 */
	private static List<File> 
	unTar(
		final File inputFile, 
		final File outputDir
		)
		throws FileNotFoundException, IOException, ArchiveException
	{

		Utils.logInfo(
			String.format(
				"Untaring %s to dir %s.",
				inputFile.getAbsolutePath(), 
				outputDir.getAbsolutePath())
			);

		final List<File> untaredFiles = new LinkedList<File>();
		final InputStream is = new FileInputStream(inputFile);
		
		final TarArchiveInputStream debInputStream = 
			(TarArchiveInputStream) new ArchiveStreamFactory()
				.createArchiveInputStream("tar", is);
		
		TarArchiveEntry entry = null;
		
		while ((entry = (TarArchiveEntry) debInputStream.getNextEntry()) != null)
		{
			final File outputFile = new File(outputDir, entry.getName());
			
			if (entry.isDirectory())
			{
				Utils.logInfo(
					String.format(
						"Attempting to write output directory %s.",
						outputFile.getAbsolutePath())
					);
				
				if (!outputFile.exists())
				{
					Utils.logInfo(
						String.format(
							"Attempting to create output directory %s.",
							outputFile.getAbsolutePath())
						);
					
					if (!outputFile.mkdirs())
					{
						throw new IllegalStateException(
							String.format(
								"Couldn't create directory %s.",
								outputFile.getAbsolutePath())
							);
					}
				}
			} 
			else
			{
				Utils.logInfo(
					String.format(
						"Creating output file %s.",
						outputFile.getAbsolutePath())
					);
				
				final OutputStream outputFileStream = 
						new FileOutputStream(outputFile);
				
				IOUtils.copy(debInputStream, outputFileStream);
				
				outputFileStream.close();
			}
			
			untaredFiles.add(outputFile);
		}
		
		debInputStream.close();

		return untaredFiles;
	}

	/**
	 * Ungzip an input file into an output file.
	 * <p>
	 * The output file is created in the output folder, having the same name as
	 * the input file, minus the '.gz' extension.
	 * 
	 * @param inputFile the input .gz file
	 * @param outputDir the output directory file.
	 * 
	 * @throws IOException
	 * @throws FileNotFoundException
	 * 
	 * @return The {@File} with the ungzipped content.
	 */
	private static File 
	unGzip(
		final File inputFile, 
		final File outputDir
		)
		throws FileNotFoundException, IOException
	{

		Utils.logInfo(
			String.format(
				"Ungzipping %s to dir %s.",
				inputFile.getAbsolutePath(), 
				outputDir.getAbsolutePath())
			);

		final File outputFile = 
			new File(outputDir, inputFile.getName()
				.substring(0, inputFile.getName().length() - 3));

		final GZIPInputStream in = 
			new GZIPInputStream(new FileInputStream(inputFile));
		
		final FileOutputStream out = new FileOutputStream(outputFile);

		byte[] buffer;
    	int numBytes;
    	
    	buffer = new byte[1024];
		
    	while ((numBytes = in.read(buffer)) != -1)
		{
			out.write(buffer);
		}
		
		in.close();
		out.close();

		return outputFile;
	}
	
	public static void
	extractDataDir(
		String dataTarBall
		)
		throws Exception
	{
		File fileDataTar;
		
		fileDataTar = new File(dataTarBall);
				
		unGzip(new File(dataTarBall), fileDataTar.getParentFile());
		
		dataTarBall = dataTarBall.replace(".gz", "");
		
		unTar(new File(dataTarBall), new File("/data/data/"));
	}
}
